package ca.polymtl.rubikcube.CubeActivity;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Vector;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.util.Log;
import android.view.MotionEvent;
import ca.polymtl.rubikcube.CubeActivity.model.Rubik;
import ca.polymtl.rubikcube.CubeActivity.model.Rubik.RotationDirection;
import ca.polymtl.rubikcube.CubeActivity.model.Side;
import ca.polymtl.rubikcube.util.Color;
import ca.polymtl.rubikcube.util.Position;
import ca.polymtl.rubikcube.util.PositionSide;
import ca.polymtl.rubikcube.util.Rotation;
import ca.polymtl.rubikcube.util.Vect3f;

public class CubeSurfaceViewRenderer implements GLSurfaceView.Renderer {
	final static String LOG_TAG = CubeSurfaceViewRenderer.class.getSimpleName();

	private final Context context;

	Vector<PositionSide[]> cubeletsSides;
	Vector<PositionSide> selectedCubelets;
	
	RotationDirection currentRotationDirection = RotationDirection.CW;
	
	public CubeSurfaceViewRenderer(Context context) {
		this.context = context;
		selectedCubelets = new Vector<PositionSide>();
		cubeletsSides = new Vector<PositionSide[]>();
		PositionSide[] cubeletsFRONT = { new PositionSide(new Position(2, 2, 2), new Vector(Arrays.asList(Side.RIGHT.ordinal(), Side.UP.ordinal()))), new PositionSide(new Position(1, 2, 2), new Vector(Arrays.asList(Side.UP.ordinal()))), new PositionSide(new Position(0, 2, 2), new Vector(Arrays.asList(Side.UP.ordinal(), Side.LEFT.ordinal()))), new PositionSide(new Position(0, 1, 2), new Vector(Arrays.asList(Side.LEFT.ordinal()))), new PositionSide(new Position(0, 0, 2), new Vector(Arrays.asList(Side.LEFT.ordinal(), Side.DOWN.ordinal()))), new PositionSide(new Position(1, 0, 2), new Vector(Arrays.asList(Side.DOWN.ordinal()))), new PositionSide(new Position(2, 0, 2), new Vector(Arrays.asList(Side.DOWN.ordinal(), Side.RIGHT.ordinal()))), new PositionSide(new Position(2, 1, 2), new Vector(Arrays.asList(Side.RIGHT.ordinal()))) };
		PositionSide[] cubeletsMIDDLE_FB = { new PositionSide(new Position(2, 2, 1), new Vector(Arrays.asList(Side.RIGHT.ordinal(), Side.UP.ordinal()))), new PositionSide(new Position(1, 2, 1), new Vector(Arrays.asList(Side.UP.ordinal()))), new PositionSide(new Position(0, 2, 1), new Vector(Arrays.asList(Side.UP.ordinal(), Side.LEFT.ordinal()))), new PositionSide(new Position(0, 1, 1), new Vector(Arrays.asList(Side.LEFT.ordinal()))), new PositionSide(new Position(0, 0, 1), new Vector(Arrays.asList(Side.LEFT.ordinal(), Side.DOWN.ordinal()))), new PositionSide(new Position(1, 0, 1), new Vector(Arrays.asList(Side.DOWN.ordinal()))), new PositionSide(new Position(2, 0, 1), new Vector(Arrays.asList(Side.DOWN.ordinal(), Side.RIGHT.ordinal()))), new PositionSide(new Position(2, 1, 1), new Vector(Arrays.asList(Side.RIGHT.ordinal()))) };
		PositionSide[] cubeletsBACK = { new PositionSide(new Position(2, 2, 0), new Vector(Arrays.asList(Side.RIGHT.ordinal(), Side.UP.ordinal()))), new PositionSide(new Position(1, 2, 0), new Vector(Arrays.asList(Side.UP.ordinal()))), new PositionSide(new Position(0, 2, 0), new Vector(Arrays.asList(Side.UP.ordinal(), Side.LEFT.ordinal()))), new PositionSide(new Position(0, 1, 0), new Vector(Arrays.asList(Side.LEFT.ordinal()))), new PositionSide(new Position(0, 0, 0), new Vector(Arrays.asList(Side.LEFT.ordinal(), Side.DOWN.ordinal()))), new PositionSide(new Position(1, 0, 0), new Vector(Arrays.asList(Side.DOWN.ordinal()))), new PositionSide(new Position(2, 0, 0), new Vector(Arrays.asList(Side.DOWN.ordinal(), Side.RIGHT.ordinal()))), new PositionSide(new Position(2, 1, 0), new Vector(Arrays.asList(Side.RIGHT.ordinal()))) };
		PositionSide[] cubeletsUP = { new PositionSide(new Position(2, 2, 2), new Vector(Arrays.asList(Side.RIGHT.ordinal(), Side.FRONT.ordinal()))), new PositionSide(new Position(1, 2, 2), new Vector(Arrays.asList(Side.FRONT.ordinal()))), new PositionSide(new Position(0, 2, 2), new Vector(Arrays.asList(Side.FRONT.ordinal(), Side.LEFT.ordinal()))), new PositionSide(new Position(0, 2, 1), new Vector(Arrays.asList(Side.LEFT.ordinal()))), new PositionSide(new Position(0, 2, 0), new Vector(Arrays.asList(Side.LEFT.ordinal(), Side.BACK.ordinal()))), new PositionSide(new Position(1, 2, 0), new Vector(Arrays.asList(Side.BACK.ordinal()))), new PositionSide(new Position(2, 2, 0), new Vector(Arrays.asList(Side.BACK.ordinal(), Side.RIGHT.ordinal()))), new PositionSide(new Position(2, 2, 1), new Vector(Arrays.asList(Side.RIGHT.ordinal()))) };
		PositionSide[] cubeletsMIDDLE_UD = { new PositionSide(new Position(2, 1, 2), new Vector(Arrays.asList(Side.RIGHT.ordinal(), Side.FRONT.ordinal()))), new PositionSide(new Position(1, 1, 2), new Vector(Arrays.asList(Side.FRONT.ordinal()))), new PositionSide(new Position(0, 1, 2), new Vector(Arrays.asList(Side.FRONT.ordinal(), Side.LEFT.ordinal()))), new PositionSide(new Position(0, 1, 1), new Vector(Arrays.asList(Side.LEFT.ordinal()))), new PositionSide(new Position(0, 1, 0), new Vector(Arrays.asList(Side.LEFT.ordinal(), Side.BACK.ordinal()))), new PositionSide(new Position(1, 1, 0), new Vector(Arrays.asList(Side.BACK.ordinal()))), new PositionSide(new Position(2, 1, 0), new Vector(Arrays.asList(Side.BACK.ordinal(), Side.RIGHT.ordinal()))), new PositionSide(new Position(2, 1, 1), new Vector(Arrays.asList(Side.RIGHT.ordinal()))) };
		PositionSide[] cubeletsDOWN =	{ new PositionSide(new Position(2, 0, 2), new Vector(Arrays.asList(Side.RIGHT.ordinal(), Side.FRONT.ordinal()))), new PositionSide(new Position(1, 0, 2), new Vector(Arrays.asList(Side.FRONT.ordinal()))), new PositionSide(new Position(0, 0, 2), new Vector(Arrays.asList(Side.FRONT.ordinal(), Side.LEFT.ordinal()))), new PositionSide(new Position(0, 0, 1), new Vector(Arrays.asList(Side.LEFT.ordinal()))), new PositionSide(new Position(0, 0, 0), new Vector(Arrays.asList(Side.LEFT.ordinal(), Side.BACK.ordinal()))), new PositionSide(new Position(1, 0, 0), new Vector(Arrays.asList(Side.BACK.ordinal()))), new PositionSide(new Position(2, 0, 0), new Vector(Arrays.asList(Side.BACK.ordinal(), Side.RIGHT.ordinal()))), new PositionSide(new Position(2, 0, 1), new Vector(Arrays.asList(Side.RIGHT.ordinal()))) };
		PositionSide[] cubeletsRIGHT = { new PositionSide(new Position(2, 2, 2), new Vector(Arrays.asList(Side.UP.ordinal(), Side.FRONT.ordinal()))), new PositionSide(new Position(2, 1, 2), new Vector(Arrays.asList(Side.FRONT.ordinal()))), new PositionSide(new Position(2, 0, 2), new Vector(Arrays.asList(Side.FRONT.ordinal(), Side.DOWN.ordinal()))), new PositionSide(new Position(2, 0, 1), new Vector(Arrays.asList(Side.DOWN.ordinal()))), new PositionSide(new Position(2, 0, 0), new Vector(Arrays.asList(Side.DOWN.ordinal(), Side.BACK.ordinal()))), new PositionSide(new Position(2, 1, 0), new Vector(Arrays.asList(Side.BACK.ordinal()))), new PositionSide(new Position(2, 2, 0), new Vector(Arrays.asList(Side.BACK.ordinal(), Side.UP.ordinal()))), new PositionSide(new Position(2, 2, 1), new Vector(Arrays.asList(Side.UP.ordinal()))) };		
		PositionSide[] cubeletsMIDDLE_RL = { new PositionSide(new Position(1, 2, 2), new Vector(Arrays.asList(Side.UP.ordinal(), Side.FRONT.ordinal()))), new PositionSide(new Position(1, 1, 2), new Vector(Arrays.asList(Side.FRONT.ordinal()))), new PositionSide(new Position(1, 0, 2), new Vector(Arrays.asList(Side.FRONT.ordinal(), Side.DOWN.ordinal()))), new PositionSide(new Position(1, 0, 1), new Vector(Arrays.asList(Side.DOWN.ordinal()))), new PositionSide(new Position(1, 0, 0), new Vector(Arrays.asList(Side.DOWN.ordinal(), Side.BACK.ordinal()))), new PositionSide(new Position(1, 1, 0), new Vector(Arrays.asList(Side.BACK.ordinal()))), new PositionSide(new Position(1, 2, 0), new Vector(Arrays.asList(Side.BACK.ordinal(), Side.UP.ordinal()))), new PositionSide(new Position(1, 2, 1), new Vector(Arrays.asList(Side.UP.ordinal()))) };
		PositionSide[] cubeletsLEFT = { new PositionSide(new Position(0, 2, 2), new Vector(Arrays.asList(Side.UP.ordinal(), Side.FRONT.ordinal()))), new PositionSide(new Position(0, 1, 2), new Vector(Arrays.asList(Side.FRONT.ordinal()))), new PositionSide(new Position(0, 0, 2), new Vector(Arrays.asList(Side.FRONT.ordinal(), Side.DOWN.ordinal()))), new PositionSide(new Position(0, 0, 1), new Vector(Arrays.asList(Side.DOWN.ordinal()))), new PositionSide(new Position(0, 0, 0), new Vector(Arrays.asList(Side.DOWN.ordinal(), Side.BACK.ordinal()))), new PositionSide(new Position(0, 1, 0), new Vector(Arrays.asList(Side.BACK.ordinal()))), new PositionSide(new Position(0, 2, 0), new Vector(Arrays.asList(Side.BACK.ordinal(), Side.UP.ordinal()))), new PositionSide(new Position(0, 2, 1), new Vector(Arrays.asList(Side.UP.ordinal()))) };
		
		cubeletsSides.add(cubeletsFRONT);
		cubeletsSides.add(cubeletsUP);
		cubeletsSides.add(cubeletsRIGHT);
		cubeletsSides.add(cubeletsBACK);
		cubeletsSides.add(cubeletsDOWN);
		cubeletsSides.add(cubeletsLEFT);
		cubeletsSides.add(cubeletsMIDDLE_FB);
		cubeletsSides.add(cubeletsMIDDLE_UD);
		cubeletsSides.add(cubeletsMIDDLE_RL);
	}


	public int cubeletBelongSide(PositionSide cubeletPos, int sideNb)
	{
		int belongSide = -1;
		for (int cubeletPosSide=0; cubeletPosSide<8; cubeletPosSide++) {
			if (cubeletPos.getPos().getX() == cubeletsSides.get(sideNb)[cubeletPosSide].getPos().getX() 
					&& cubeletPos.getPos().getY() == cubeletsSides.get(sideNb)[cubeletPosSide].getPos().getY()
					&&  cubeletPos.getPos().getZ() == cubeletsSides.get(sideNb)[cubeletPosSide].getPos().getZ()) {
				for (int cubeletSide=0; cubeletSide<cubeletsSides.get(sideNb)[cubeletPosSide].getSides().size(); cubeletSide++) {
					Log.d("SELECTION","ohoh"+ cubeletsSides.get(sideNb)[cubeletPosSide].getSides().get(cubeletSide));
					if (cubeletPos.getSides().get(0) == cubeletsSides.get(sideNb)[cubeletPosSide].getSides().get(cubeletSide)) {
						belongSide = cubeletPosSide;
					}
				}
				
			}
		}
		return belongSide;
	} 
	
	public Side getRotateSide(Vector<PositionSide> selectedCubelets)
	{
		Vector rotationWay = new Vector<Integer>();
		boolean ccwWay = false;
		Side rotateSide = null;
		int arrayPos = -1;
		boolean selectedCubeletsBelongSide = false;
		for (int sideNb=0; sideNb<cubeletsSides.size(); sideNb++) {
			selectedCubeletsBelongSide = true;
			rotationWay.clear();
			for (int cubeletNb=0; cubeletNb<selectedCubelets.size(); cubeletNb++) {
				arrayPos = cubeletBelongSide(selectedCubelets.get(cubeletNb), sideNb);
				if (arrayPos<0) {
					selectedCubeletsBelongSide = false;
					break;
				}
				else {
						rotationWay.add(arrayPos);
				}
			}
			if (selectedCubeletsBelongSide) {
				for (int i=0; i<rotationWay.size(); i++) {
					
					if ((Integer)rotationWay.get(i) < (Integer)rotationWay.get(0) && !((Integer)rotationWay.get(i)==0 && (Integer)rotationWay.get(0)>=6) || ((Integer)rotationWay.get(i) > (Integer)rotationWay.get(0) && (Integer)rotationWay.get(i)>=6 && (Integer)rotationWay.get(0)==0 ) ) {
						ccwWay = true;
						break;
					}
					Log.d("SELECTION", "i:"+(Integer)rotationWay.get(i)+",0:"+(Integer)rotationWay.get(0));
				}
				if (sideNb == Side.FRONT.ordinal()) {
					rotateSide = Side.FRONT;
				}
				else if (sideNb == Side.BACK.ordinal()) {
					ccwWay = !ccwWay;
					rotateSide = Side.BACK;
				}
				else if (sideNb == Side.UP.ordinal()) {
					ccwWay = !ccwWay;
					rotateSide = Side.UP;
				}
				else if (sideNb == Side.DOWN.ordinal()) {
					rotateSide = Side.DOWN;
				}
				else if (sideNb == Side.RIGHT.ordinal()) {
					rotateSide = Side.RIGHT;
				}
				else if (sideNb == Side.LEFT.ordinal()) {
					ccwWay = !ccwWay;
					rotateSide = Side.LEFT;
				}
				else if (sideNb == Side.MIDDLE_FB.ordinal()) {
					ccwWay = !ccwWay;
					rotateSide = Side.MIDDLE_FB;
				}
				else if (sideNb == Side.MIDDLE_UD.ordinal()) {
					rotateSide = Side.MIDDLE_UD;
				}
				else if (sideNb == Side.MIDDLE_RL.ordinal()) {
					ccwWay = !ccwWay;
					rotateSide = Side.MIDDLE_RL;
				}
				currentRotationDirection = (ccwWay == true) ? RotationDirection.CW : RotationDirection.CCW;
				break;
			}
		}
		return rotateSide;
	}
	
	/*
	 * Background color (clear color).
	 */
	private final float CLEAR_R = 0.5f;
	private final float CLEAR_G = 0.5f;
	private final float CLEAR_B = 0.5f;

	private final float eyeZMax = 18f;
	private final float eyeZMin = 6f;

	/**
	 * Current orientation of the view.
	 */
	Rotation orientation = new Rotation();

	/* For touch rotation */
	float lastTouchX = 0f;
	float lastTouchY = 0f;

	float lastTouchsX[] = { 0, 0, 0, 0, 0 };
	float lastTouchsY[] = { 0, 0, 0, 0, 0 };

	double oldDist2Fingers = 0;
	final float ROTATION_ANGLE_FOR_1_PIXEL = -(float) Math.PI / 600f;
	boolean inMotion = false;

	boolean firstCubeletSaved = false;
	
	boolean picked = false;
	boolean inCube = false;
	float pickX, pickY;

	float eyeZ = 7;

	boolean printSelected = false;
	
	// current number of fingers on the screen
	int nbPointers = 0;
	
	/**
	 * Standard type sizes.
	 */
	int FLOATSIZE = Float.SIZE / 8;
	int SHORTSIZE = Short.SIZE / 8;
	int INTSIZE = Integer.SIZE / 8;
	int BYTESIZE = Byte.SIZE / 8;

	/**
	 * The data model of the cube.
	 */
	Rubik rubik = null;
	AnimationCompleteCallback animationCompleteCallback = null;

	/**
	 * The utility used to render the cube in 3D with OpenGL.
	 */
	CubeRenderer _cubeRenderer = null;

	/**
	 * Viewport height and width.
	 */
	int viewportW = 0;
	int viewportH = 0;

	public void draw(GL10 gl, boolean picking, boolean pickingSide) {
		/*
		 * Clear the color and depth buffer
		 */
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

		gl.glMatrixMode(GL10.GL_MODELVIEW);
		/*
		 * Load identity
		 */
		gl.glLoadIdentity();

		GLU.gluLookAt(gl, 0f, 0f, eyeZ, // Position
				0f, 0f, 0f, // Look
				0f, 1f, 0f // Up
		);

		/*
		 * Take in account the current orientation.
		 */
		gl.glMultMatrixf(orientation.asFloatBuffer());
		
		/*
		 * Render the cube.
		 */
		_cubeRenderer.render(gl, picking, pickingSide);

	}

	public Side pickSide(GL10 gl)
	{
		Side side = null;
		String sideStr = "";
		ByteBuffer buf = ByteBuffer.allocateDirect(4);
		gl.glReadPixels((int) pickX, viewportH - (int) pickY, 1, 1, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, buf);
		int sideColor[] = {0, 0, 0};
		for (int cubletPos=0; cubletPos<3; cubletPos++) {
			sideColor[cubletPos] = (int) buf.get() & 0x000000ff;
			if (sideColor[cubletPos] <= 60) {
				sideColor[cubletPos] = 0;
			}
			else {
				sideColor[cubletPos] = 255;
			}
		}
		int a = (int) buf.get() & 0x000000ff;
		if (sideColor[0] > 0 && sideColor[1] > 0 && sideColor[2] == 0) {
			side = Side.FRONT;
			sideStr = "FRONT";
		}
		else if (sideColor[0] > 0 && sideColor[1] == 0 && sideColor[2] == 0) {
			side = Side.BACK;
			sideStr = "BACK";
		}
		else if (sideColor[0] == 0 && sideColor[1] > 0 && sideColor[2] > 0) {
			side = Side.UP;
			sideStr = "UP";
		}
		else if (sideColor[0] == 0 && sideColor[1] > 0 && sideColor[2] == 0) {
			side = Side.DOWN;
			sideStr = "DOWN";
		}
		else if (sideColor[0] > 0 && sideColor[1] == 0 && sideColor[2] > 0) {
			side = Side.RIGHT;
			sideStr = "RIGHT";
		}
		else if (sideColor[0] == 0 && sideColor[1] == 0 && sideColor[2] > 0) {
			side = Side.LEFT;
			sideStr = "LEFT";
		}
		
		Log.d("SELECTION", "sideColor0: " + sideColor[0] +", sideColor1: " + sideColor[1] + ", sideColor2: " + sideColor[2]);
		Log.d("SELECTION", "side: " + sideStr);
		return side;
	}
	
	public void pick(GL10 gl) {
		ByteBuffer buf = ByteBuffer.allocateDirect(4);
		gl.glReadPixels((int) pickX, viewportH - (int) pickY, 1, 1, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, buf);

		int cubletPosArray[] = {0, 0, 0};
		
		for (int cubletPos=0; cubletPos<3; cubletPos++) {
			cubletPosArray[cubletPos] = (int) buf.get() & 0x000000ff;
			if (cubletPosArray[cubletPos] <= 60 && cubletPosArray[cubletPos] >= 20) {
				cubletPosArray[cubletPos] = 40;
			}
			else if (cubletPosArray[cubletPos] <= 140 && cubletPosArray[cubletPos] >= 100) {
				cubletPosArray[cubletPos] = 120;
			}
			else if (cubletPosArray[cubletPos] <= 220 && cubletPosArray[cubletPos] >= 180) {
				cubletPosArray[cubletPos] = 200;
			}
		}
		
		int i = (cubletPosArray[0] - 40) / 80;
		int j = (cubletPosArray[1] - 40) / 80;
		int k = (cubletPosArray[2] - 40) / 80;
		int a = (int) buf.get() & 0x000000ff;

		if (i >= 0 && i <= 2 && j >= 0 && j <= 2 && k >= 0 && k <= 2 && (i != 1 || j != 1 || k != 1) ) {
			//Log.d("SELECTION", "i:" + i + ", j:" + j + ", k:" + k);
			//rubik.cubeletAt(i, j, k).setAllSidesColor(Color.BLACK);
			inCube = true;
			Position cubeletPos = new Position(i, j, k);
			
			draw(gl, true, true);
			Side side=null;
			side = pickSide(gl);
			if (side != null) {
				if (selectedCubelets.size() == 0 || ( (selectedCubelets.get(0).getPos().getX() != cubeletPos.getX() || selectedCubelets.get(0).getPos().getY() != cubeletPos.getY() || selectedCubelets.get(0).getPos().getZ() != cubeletPos.getZ()) && selectedCubelets.size() < 2))
				selectedCubelets.add(new PositionSide(cubeletPos, new Vector(Arrays.asList(side.ordinal()))));
			}
		} else {
			inCube = false;
		}
		Log.d("CUBE", "a = " + a);
	}

	/**
	 * Called when comes the time to draw a frame.
	 */
	public void onDrawFrame(GL10 gl) {
		if (picked) {
			draw(gl, true, false);
			pick(gl);
			//draw(gl, true, true);
			//pickSide(gl);
			picked = false;
		}
		draw(gl, false, false);
		int sideNb = -1;
		if (printSelected) {
			if(selectedCubelets.size() > 1) {
				for (int i=0; i<selectedCubelets.size(); i++) {
					if ((selectedCubelets.get(i)).getSides().size() > 0) {
						sideNb = (Integer) (selectedCubelets.get(i)).getSides().get(0);
					}
					Log.d("SELECTION", "cublet nb:" + i + ": " + (selectedCubelets.get(i)).getPos().getX() + "," +  (selectedCubelets.get(i)).getPos().getY() + "," +  (selectedCubelets.get(i)).getPos().getZ() + ", Side: " + sideNb);
				}
				Side rotateSide = getRotateSide(selectedCubelets);
				if (rotateSide != null) {
					rotateFace(rotateSide, currentRotationDirection);
					Log.d("SELECTION", "SideNB: " + rotateSide.ordinal());
				}
			}
			else {
				selectedCubelets.clear();
			}
			printSelected = false;
		}
	}

	/**
	 * Adds a rotation to the current orientation of the object.
	 * 
	 * @param r
	 *            The rotation to add to the current orientation.
	 */
	public void rotate(Rotation r) {
		orientation = orientation.add(r);
	}

	/**
	 * Called when the surface is modified (including when it is created). We update the viewport size.
	 */
	public void onSurfaceChanged(GL10 gl, int width, int height) {

		viewportW = width;
		viewportH = height;
		gl.glViewport(0, 0, width, height);

		/*
		 * Set up the projection.
		 */
		gl.glMatrixMode(GL10.GL_PROJECTION);
		gl.glLoadIdentity();
		gl.glFrustumf(-0.5f, // left
				0.5f, // right
				-0.5f * viewportH / viewportW, // DOWN
				0.5f * viewportH / viewportW, // UP
				1.5f, // nearVal
				20f // farVal
		);

		Log.d("CUBE", "CubeSurfaceViewRenderer onSurfaceChanged");
	}

	/**
	 * Called when the surface is created.
	 */
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		glInit(gl);

		Log.d("CUBE", "CubeSurfaceViewRenderer onSurfaceCreated");
	}

	/**
	 * Initializes OpenGL like we want.
	 * 
	 * @param gl
	 *            GL content
	 */
	private void glInit(GL10 gl) {
		Log.d("CUBE", "CubeSurfaceViewRenderer glInit");

		gl.glClearColor(CLEAR_R, CLEAR_G, CLEAR_B, 1.0f);

		/* Avoid rendering faces not visible */
		gl.glEnable(GL10.GL_CULL_FACE);
		/* Front faces are those that are drawed counter clockwise */
		gl.glFrontFace(GL10.GL_CCW);
		/* Don't render back faces */
		gl.glCullFace(GL10.GL_BACK);

		gl.glEnable(GL10.GL_BLEND);

		/* Depth test */
		gl.glEnable(GL10.GL_DEPTH_TEST);
		gl.glDepthFunc(GL10.GL_LEQUAL);

		/* Enable textures */
		gl.glEnable(GL10.GL_TEXTURE_2D);

		gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);

		_cubeRenderer = new CubeRenderer(gl);
		_cubeRenderer.setRubik(rubik);
		_cubeRenderer.setAnimationCompleteCallback(animationCompleteCallback);
		_cubeRenderer.loadGLTexture(gl, context);
	}


	class TmpPos {
		final public float x, y;

		TmpPos(float x, float y) {
			this.x = x;
			this.y = y;
		}
	}

	final int NB_SMOOTH = 5;
	LinkedList<TmpPos> smoother = new LinkedList<CubeSurfaceViewRenderer.TmpPos>();

	

	public void touchEventAction(MotionEvent event) {

		if (event.getPointerCount() != nbPointers) {
			inMotion = false;
		}
		nbPointers = event.getPointerCount();

		switch (event.getAction()) {
		
		case MotionEvent.ACTION_MOVE:
			/* Ignore the first move event to avoid "jumps" */
			if (!firstCubeletSaved) {
				firstCubeletSaved = true;
				picked = true;
				pickX = event.getX();
				pickY = event.getY();
				selectedCubelets.clear();
			}
			else {
				picked = true;
				pickX = event.getX();
				pickY = event.getY();
			}
			if (inMotion) {

				final float dx = event.getX() - lastTouchX;
				final float dy = event.getY() - lastTouchY;

				float dxs[] = { 0, 0, 0, 0, 0 };
				float dys[] = { 0, 0, 0, 0, 0 };

				if (event.getPointerCount() == 2) {
					double diff = spacing2Fingers(event) - oldDist2Fingers;
					zoom((float) diff / 50 * -1);
				} 
				if (event.getPointerCount() >= 2 && event.getPointerCount() < 6) {
					for (int fingerNb = 0; fingerNb < event.getPointerCount(); fingerNb++) {
						dxs[fingerNb] = event.getX(fingerNb) - lastTouchsX[fingerNb];
						dys[fingerNb] = event.getY(fingerNb) - lastTouchsY[fingerNb];
					}
				}

				float final_dx = 0f, final_dy = 0f;
				float final_dxs[] = { 0, 0, 0, 0, 0 }, final_dys[] = { 0, 0, 0, 0, 0 };

				final_dx = dx;
				final_dy = dy;

				if (event.getPointerCount() >= 2 && event.getPointerCount() < 6) {
					for (int fingerNb = 0; fingerNb < event.getPointerCount(); fingerNb++) {
						final_dxs[fingerNb] = dxs[fingerNb];
						final_dys[fingerNb] = dys[fingerNb];
					}
				}
				lastTouchX = event.getX();
				lastTouchY = event.getY();
				if (event.getPointerCount() == 2) {
					oldDist2Fingers = spacing2Fingers(event);
				} 
				if (event.getPointerCount() >= 2 && event.getPointerCount() < 6) {
					double atanT = Math.atan((double) viewportW / (double) viewportH);
					int value = movementType(event, dxs, dys);

					for (int fingerNb = 0; fingerNb < event.getPointerCount(); fingerNb++) {
						lastTouchsX[fingerNb] = event.getX(fingerNb);
						lastTouchsY[fingerNb] = event.getY(fingerNb);

						if (value > 0) {
							float factor = 1f;
							double atanA = Math.atan(event.getX(fingerNb) / event.getY(fingerNb));
							if (atanA > atanT) {
								factor = -1f;
							}
							rotate(new Rotation(ROTATION_ANGLE_FOR_1_PIXEL * final_dxs[fingerNb], new Vect3f(0, 0,
									factor)));
							rotate(new Rotation(ROTATION_ANGLE_FOR_1_PIXEL * final_dys[fingerNb], new Vect3f(0, 0,
									factor)));
						}
						if (value < 2) {
							rotate(new Rotation(ROTATION_ANGLE_FOR_1_PIXEL * final_dxs[fingerNb], new Vect3f(0, 1, 0)));
							rotate(new Rotation(ROTATION_ANGLE_FOR_1_PIXEL * final_dys[fingerNb], new Vect3f(1, 0, 0)));
						}
					}
				}

			} else {
				lastTouchX = event.getX();
				lastTouchY = event.getY();
				if (event.getPointerCount() == 2) {
					oldDist2Fingers = spacing2Fingers(event);
				} 
				if (event.getPointerCount() >= 2 && event.getPointerCount() < 6) {
					for (int fingerNb = 0; fingerNb < event.getPointerCount(); fingerNb++) {
						lastTouchsX[fingerNb] = event.getX(fingerNb);
						lastTouchsY[fingerNb] = event.getY(fingerNb);
					}
				}
			}
			inMotion = true;
			break;
		case MotionEvent.ACTION_UP:
			Log.d("SELECTION", "ACTION_UP");
			//if (!inMotion) {
				Log.d("CUBE", "Do selection at (" + event.getX() + "," + event.getY() + ")");
				pickX = event.getX();
				pickY = event.getY();
				picked = true;
				printSelected = true;
				firstCubeletSaved = false;
				Log.d("SELECTION", "--------------------");
			//}
			inMotion = false;
			break;
		case MotionEvent.ACTION_DOWN:
			Log.d("SELECTION", "ACTION_DOWN");
			if (!firstCubeletSaved) {
				firstCubeletSaved = true;
				picked = true;
				pickX = event.getX();
				pickY = event.getY();
				selectedCubelets.clear();
			}
			
			lastTouchX = event.getX();
			lastTouchY = event.getY();
			if (event.getPointerCount() == 2) {
				oldDist2Fingers = spacing2Fingers(event);
			} 
			if (event.getPointerCount() >= 2 && event.getPointerCount() < 6) {
				for (int fingerNb = 0; fingerNb < event.getPointerCount(); fingerNb++) {
					lastTouchsX[fingerNb] = event.getX(fingerNb);
					lastTouchsY[fingerNb] = event.getY(fingerNb);
				}
			}
			inMotion = false;
			break;
		}
	}

	public void rotateFace(Side side, RotationDirection direction) {
		_cubeRenderer.startRotateFace(side, direction);
		//if (!animation) {
		//	_cubeRenderer.finishRotateFace();
		//}
	}

	public void switchRotateDirection() {
		//currentRotationDirection = (currentRotationDirection == RotationDirection.CCW) ? RotationDirection.CW
			//	: RotationDirection.CCW;
	}

	boolean antiParkinson = false;

	public void toggleParkinson() {
		switchRotateDirection();
		antiParkinson = !antiParkinson;
	}

	public void zoom(float factor) {

		if (eyeZ + factor < eyeZMax && eyeZ + factor > eyeZMin) {
			eyeZ = eyeZ + factor;
		}
	}

	private double spacing2Fingers(MotionEvent event) {
		float x = event.getX(0) - event.getX(1);
		float y = event.getY(0) - event.getY(1);
		float diff2Fing = x * x + y * y;

		return Math.sqrt(diff2Fing);
	}

	private int movementType(MotionEvent event, float dxs[], float dys[]) {
		boolean isX = false;
		boolean isY = false;
		int type = 0;
		if (event.getPointerCount() >= 2 && event.getPointerCount() < 6) {
			boolean dxPositive = (dxs[0] > 0) ? true : false;
			boolean dyPositive = (dys[0] > 0) ? true : false;
			for (int fingerNb = 0; fingerNb < event.getPointerCount(); fingerNb++) {
				if (dxPositive) {
					if (dxs[fingerNb] < 0) {
						isX = true;
					}
				} else {
					if (dxs[fingerNb] > 0) {
						isX = true;
					}
				}
				if (dyPositive) {
					if (dys[fingerNb] < 0) {
						isY = true;
					}
				} else {
					if (dys[fingerNb] > 0) {
						isY = true;
					}
				}
			}
			if (!isX && !isY) {
				type = 0;
			} else if (!isX || !isY) {
				type = 1;
			} else {
				type = 2;
			}
		}
		return type;
	}

	public void setRubik(Rubik rubik) {
		this.rubik = rubik;
		
		if (_cubeRenderer != null) {
			_cubeRenderer.setRubik(rubik);
		}
	}
	
	public interface AnimationCompleteCallback {
		public void animationCompleted();
	}

	public void setAnimationCompleteCallback(AnimationCompleteCallback callback) {
		this.animationCompleteCallback = callback;
		
		if (_cubeRenderer != null) {
			_cubeRenderer.setAnimationCompleteCallback(callback);
		}
	}

	public void finishRotateFace() {
		_cubeRenderer.finishRotateFace();
	}
}
